/*
 * C++ Design Patterns: Visitor
 * Author: Jakub Vojvoda [github.com/JakubVojvoda]
 * 2016
 *
 * Source code is licensed under MIT License
 * (for more details see LICENSE)
 *
 */

#include <iostream>

class Element;
class ConcreteElementA;
class ConcreteElementB;

/*
 * Visitor
 * declares a Visit operation for each class of ConcreteElement
 * in the object structure
 */
class Visitor {
   public:
      virtual ~Visitor() {}

      virtual void visitElementA( ConcreteElementA* const element ) = 0;
      virtual void visitElementB( ConcreteElementB* const element ) = 0;
      // ...
};

/*
 * Concrete Visitors
 * implement each operation declared by Visitor, which implement
 * a fragment of the algorithm defined for the corresponding class
 * of object in the structure
 */
class ConcreteVisitor1: public Visitor {
   public:
      ~ConcreteVisitor1() {}

      void visitElementA( ConcreteElementA* const ) {
         std::cout << "Concrete Visitor 1: Element A visited." << std::endl;
      }

      void visitElementB( ConcreteElementB* const ) {
         std::cout << "Concrete Visitor 1: Element B visited." << std::endl;
      }

      // ...
};

class ConcreteVisitor2: public Visitor {
   public:
      ~ConcreteVisitor2() {}

      void visitElementA( ConcreteElementA* const ) {
         std::cout << "Concrete Visitor 2: Element A visited." << std::endl;
      }

      void visitElementB( ConcreteElementB* const ) {
         std::cout << "Concrete Visitor 2: Element B visited." << std::endl;
      }

      // ...
};

/*
 * Element
 * defines an accept operation that takes a visitor as an argument
 */
class Element {
   public:
      virtual ~Element() {}

      virtual void accept( Visitor& visitor ) = 0;
      // ...
};

/*
 * Concrete Elements
 * implement an accept operation that takes a visitor as an argument
 */
class ConcreteElementA: public Element {
   public:
      ~ConcreteElementA() {}

      void accept( Visitor& visitor ) { visitor.visitElementA( this ); }

      // ...
};

class ConcreteElementB: public Element {
   public:
      ~ConcreteElementB() {}

      void accept( Visitor& visitor ) { visitor.visitElementB( this ); }

      // ...
};

int main() {
   ConcreteElementA elementA;
   ConcreteElementB elementB;

   ConcreteVisitor1 visitor1;
   ConcreteVisitor2 visitor2;

   elementA.accept( visitor1 );
   elementA.accept( visitor2 );

   elementB.accept( visitor1 );
   elementB.accept( visitor2 );

   return 0;
}
